public ScrambleRequest(String title, String scrambleRequestUrl, String seed) throws InvalidScrambleRequestException, UnsupportedEncodingException { String[] puzzle_count_copies_scheme = scrambleRequestUrl.split("\\*"); title = URLDecoder.decode(title, "utf-8"); for (int i = 0; i < puzzle_count_copies_scheme.length; i++) { puzzle_count_copies_scheme[i] = URLDecoder.decode(puzzle_count_copies_scheme[i], "utf-8"); } String countStr = ""; String copiesStr = ""; String scheme = ""; String puzzle; switch (puzzle_count_copies_scheme.length) { case 4: scheme = puzzle_count_copies_scheme[3]; case 3: copiesStr = puzzle_count_copies_scheme[2]; case 2: countStr = puzzle_count_copies_scheme[1]; case 1: puzzle = puzzle_count_copies_scheme[0]; break; default: throw new InvalidScrambleRequestException("Invalid puzzle request " + scrambleRequestUrl); } LazyInstantiator<Puzzle> lazyScrambler = puzzles.get(puzzle); if (lazyScrambler == null) { throw new InvalidScrambleRequestException("Invalid scrambler: " + puzzle); } try { this.scrambler = lazyScrambler.cachedInstance(); } catch (Exception e) { throw new InvalidScrambleRequestException(e); } ScrambleCacher scrambleCacher = scrambleCachers.get(puzzle); if (scrambleCacher == null) { scrambleCacher = new ScrambleCacher(scrambler); scrambleCachers.put(puzzle, scrambleCacher); } this.title = title; fmc = countStr.equals("fmc"); int count; if (fmc) { count = 1; } else { count = Math.min(toInt(countStr, 1), MAX_COUNT); } this.copies = Math.min(toInt(copiesStr, 1), MAX_COPIES); if (seed != null) { this.scrambles = scrambler.generateSeededScrambles(seed, count); } else { this.scrambles = scrambleCacher.newScrambles(count); } this.colorScheme = scrambler.parseColorScheme(scheme); }
private static TableAndHighlighting createTable( PdfWriter docWriter, Document doc, float sideMargins, Dimension scrambleImageSize, String[] scrambles, Puzzle scrambler, HashMap<String, Color> colorScheme, String scrambleNumberPrefix, boolean forceHighlighting) throws DocumentException { PdfContentByte cb = docWriter.getDirectContent(); PdfPTable table = new PdfPTable(3); int charsWide = scrambleNumberPrefix.length() + 1 + (int) Math.log10(scrambles.length); String wideString = ""; for (int i = 0; i < charsWide; i++) { // M has got to be as wide or wider than the widest digit in our font wideString += "M"; } wideString += "."; float col1Width = new Chunk(wideString).getWidthPoint(); // I don't know why we need this, perhaps there's some padding? col1Width += 5; float availableWidth = doc.getPageSize().getWidth() - sideMargins; float scrambleColumnWidth = availableWidth - col1Width - scrambleImageSize.width - 2 * SCRAMBLE_IMAGE_PADDING; int availableScrambleHeight = scrambleImageSize.height - 2 * SCRAMBLE_IMAGE_PADDING; table.setTotalWidth( new float[] { col1Width, scrambleColumnWidth, scrambleImageSize.width + 2 * SCRAMBLE_IMAGE_PADDING }); table.setLockedWidth(true); String longestScramble = ""; String longestPaddedScramble = ""; for (String scramble : scrambles) { if (scramble.length() > longestScramble.length()) { longestScramble = scramble; } String paddedScramble = padTurnsUniformly(scramble, "M"); if (paddedScramble.length() > longestPaddedScramble.length()) { longestPaddedScramble = paddedScramble; } } // I don't know how to configure ColumnText.fitText's word wrapping characters, // so instead, I just replace each character I don't want to wrap with M, which // should be the widest character (we're using a monospaced font, // so that doesn't really matter), and won't get wrapped. char widestCharacter = 'M'; longestPaddedScramble = longestPaddedScramble.replaceAll("\\S", widestCharacter + ""); boolean tryToFitOnOneLine = true; if (longestPaddedScramble.indexOf("\n") >= 0) { // If the scramble contains newlines, then we *only* allow wrapping at the // newlines. longestPaddedScramble = longestPaddedScramble.replaceAll(" ", "M"); tryToFitOnOneLine = false; } boolean oneLine = false; Font scrambleFont = null; try { BaseFont courier = BaseFont.createFont(BaseFont.COURIER, BaseFont.CP1252, BaseFont.EMBEDDED); Rectangle availableArea = new Rectangle( scrambleColumnWidth - 2 * SCRAMBLE_PADDING_HORIZONTAL, availableScrambleHeight - SCRAMBLE_PADDING_VERTICAL_TOP - SCRAMBLE_PADDING_VERTICAL_BOTTOM); float perfectFontSize = fitText( new Font(courier), longestPaddedScramble, availableArea, MAX_SCRAMBLE_FONT_SIZE, true); if (tryToFitOnOneLine) { String longestScrambleOneLine = longestScramble.replaceAll(".", widestCharacter + ""); float perfectFontSizeForOneLine = fitText( new Font(courier), longestScrambleOneLine, availableArea, MAX_SCRAMBLE_FONT_SIZE, false); oneLine = perfectFontSizeForOneLine >= MINIMUM_ONE_LINE_FONT_SIZE; if (oneLine) { perfectFontSize = perfectFontSizeForOneLine; } } scrambleFont = new Font(courier, perfectFontSize, Font.NORMAL); } catch (IOException e) { l.log(Level.INFO, "", e); } catch (DocumentException e) { l.log(Level.INFO, "", e); } boolean highlight = forceHighlighting; for (int i = 0; i < scrambles.length; i++) { String scramble = scrambles[i]; String paddedScramble = oneLine ? scramble : padTurnsUniformly(scramble, NON_BREAKING_SPACE + ""); Chunk ch = new Chunk(scrambleNumberPrefix + (i + 1) + "."); PdfPCell nthscramble = new PdfPCell(new Paragraph(ch)); nthscramble.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); table.addCell(nthscramble); Phrase scramblePhrase = new Phrase(); int nthLine = 1; LinkedList<Chunk> lineChunks = splitScrambleToLineChunks(paddedScramble, scrambleFont, scrambleColumnWidth); if (lineChunks.size() >= MIN_LINES_TO_ALTERNATE_HIGHLIGHTING) { highlight = true; } for (Chunk lineChunk : lineChunks) { if (highlight && (nthLine % 2 == 0)) { lineChunk.setBackground(HIGHLIGHT_COLOR); } scramblePhrase.add(lineChunk); nthLine++; } PdfPCell scrambleCell = new PdfPCell(new Paragraph(scramblePhrase)); // We carefully inserted newlines ourselves to make stuff fit, don't // let itextpdf wrap lines for us. scrambleCell.setNoWrap(true); scrambleCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); // This shifts everything up a little bit, because I don't like how // ALIGN_MIDDLE works. scrambleCell.setPaddingTop(-SCRAMBLE_PADDING_VERTICAL_TOP); scrambleCell.setPaddingBottom(SCRAMBLE_PADDING_VERTICAL_BOTTOM); scrambleCell.setPaddingLeft(SCRAMBLE_PADDING_HORIZONTAL); scrambleCell.setPaddingRight(SCRAMBLE_PADDING_HORIZONTAL); // We space lines a little bit more here - it still fits in the cell height scrambleCell.setLeading(0, 1.1f); table.addCell(scrambleCell); if (scrambleImageSize.width > 0 && scrambleImageSize.height > 0) { PdfTemplate tp = cb.createTemplate( scrambleImageSize.width + 2 * SCRAMBLE_IMAGE_PADDING, scrambleImageSize.height + 2 * SCRAMBLE_IMAGE_PADDING); Graphics2D g2 = new PdfGraphics2D(tp, tp.getWidth(), tp.getHeight(), new DefaultFontMapper()); g2.translate(SCRAMBLE_IMAGE_PADDING, SCRAMBLE_IMAGE_PADDING); try { Svg svg = scrambler.drawScramble(scramble, colorScheme); drawSvgToGraphics2D(svg, g2, scrambleImageSize); } catch (Exception e) { table.addCell("Error drawing scramble: " + e.getMessage()); l.log( Level.WARNING, "Error drawing scramble, if you're having font issues, try installing ttf-dejavu.", e); continue; } finally { g2.dispose(); // iTextPdf blows up if we do not dispose of this } PdfPCell imgCell = new PdfPCell(Image.getInstance(tp), true); imgCell.setBackgroundColor(BaseColor.LIGHT_GRAY); imgCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE); imgCell.setHorizontalAlignment(PdfPCell.ALIGN_MIDDLE); table.addCell(imgCell); } else { table.addCell(""); } } TableAndHighlighting tableAndHighlighting = new TableAndHighlighting(); tableAndHighlighting.table = table; tableAndHighlighting.highlighting = highlight; return tableAndHighlighting; }