/* * Copyright 2014-15 Skynav, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.skynav.ttpe.layout; import java.io.File; import java.util.Collection; import java.util.List; import java.util.Map; import javax.xml.namespace.QName; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.skynav.ttpe.area.Area; import com.skynav.ttpe.area.AreaNode; import com.skynav.ttpe.area.BlockArea; import com.skynav.ttpe.area.BlockFillerArea; import com.skynav.ttpe.area.BlockImageArea; import com.skynav.ttpe.area.LineArea; import com.skynav.ttpe.area.ReferenceArea; import com.skynav.ttpe.fonts.FontCache; import com.skynav.ttpe.geometry.Extent; import com.skynav.ttpe.geometry.Point; import com.skynav.ttpe.geometry.TransformMatrix; import com.skynav.ttpe.geometry.WritingMode; import com.skynav.ttpe.style.BlockAlignment; import com.skynav.ttpe.style.Color; import com.skynav.ttpe.style.Defaults; import com.skynav.ttpe.style.Display; import com.skynav.ttpe.style.Image; import com.skynav.ttpe.style.StyleCollector; import com.skynav.ttpe.style.Visibility; import com.skynav.ttpe.style.Whitespace; import com.skynav.ttpe.text.LineBreakIterator; import com.skynav.ttpe.text.LineBreaker; import com.skynav.ttpe.text.Paragraph; import com.skynav.ttpe.text.ParagraphCollector; import com.skynav.ttv.app.InvalidOptionUsageException; import com.skynav.ttv.app.MissingOptionArgumentException; import com.skynav.ttv.app.OptionSpecification; import com.skynav.ttv.util.Location; import com.skynav.ttv.util.Reporter; import com.skynav.ttv.verifier.util.Integers; import com.skynav.ttv.verifier.util.NegativeTreatment; import com.skynav.ttv.verifier.util.ZeroTreatment; import com.skynav.ttx.transformer.TransformerContext; import com.skynav.xml.helpers.Documents; import static com.skynav.ttpe.parameter.Constants.*; import static com.skynav.ttpe.text.Constants.*; public class BasicLayoutProcessor extends LayoutProcessor { public static final String NAME = "basic"; public static final String defaultLineBreakerName = "uax14"; public static final String defaultCharacterBreakerName = "scalar"; // option and usage info private static final String[][] longOptionSpecifications = new String[][] { { "font", "FILE", "specify font configuration file" }, { "font-directory", "DIRECTORY","specify path to directory where font configuration files are located" }, { "default-background-color", "COLOR", "default background color (default: \"" + Defaults.getDefaultBackgroundColor() + "\")" }, { "default-color", "COLOR", "default foreground color (default: " + Defaults.getDefaultColor() + "\")" }, { "default-font-families", "FAMILIES", "default font families (default: \"" + Defaults.getDefaultFontFamilies() + "\")" }, { "default-whitespace", "SPACE", "default xml space treatment (\"default\"|\"preserve\"; default: \"" + Defaults.getDefaultWhitespace().toString().toLowerCase() + "\")" }, { "line-breaker", "NAME", "specify line breaker name (default: \"" + defaultLineBreakerName + "\")" }, { "max-regions", "COUNT", "maximum number of regions in canvas (default: no limit)" }, { "max-lines", "COUNT", "maximum number of lines in canvas (default: no limit)" }, { "max-lines-per-region", "COUNT", "maximum number of lines in a region (default: no limit)" }, { "max-chars", "COUNT", "maximum number of characters in canvas (default: no limit)" }, { "max-chars-per-region", "COUNT", "maximum number of characters in a region (default: no limit)" }, { "max-chars-per-line", "COUNT", "maximum number of characters in a line (default: no limit)" }, }; private static final Map<String,OptionSpecification> longOptions; static { longOptions = new java.util.TreeMap<String,OptionSpecification>(); for (String[] spec : longOptionSpecifications) { longOptions.put(spec[0], new OptionSpecification(spec[0], spec[1], spec[2])); } } // options state private String fontSpecificationDirectoryPath; private List<String> fontSpecificationFileNames; private String lineBreakerName; private String charBreakerName; private String defaultBackgroundColor; private String defaultColor; private String defaultFontFamilies; private String defaultWhitespace; private int maxRegions = -1; private int maxLines = -1; private int maxLinesPerRegion = -1; private int maxChars = -1; private int maxCharsPerRegion = -1; private int maxCharsPerLine = -1; // derived state private FontCache fontCache; private LineBreaker lineBreaker; private LineBreaker charBreaker; private Defaults defaults; protected BasicLayoutProcessor(TransformerContext context) { super(context); } @Override public void resetAllState(boolean restart) { resetDerivedOptionsState(restart); resetOptionsState(restart); resetGlobalState(restart); } private void resetDerivedOptionsState(boolean restart) { fontCache = null; lineBreaker = null; charBreaker = null; defaults = null; } private void resetOptionsState(boolean restart) { fontSpecificationDirectoryPath = null; fontSpecificationFileNames = null; lineBreakerName = null; charBreakerName = null; defaultBackgroundColor = null; defaultColor = null; defaultFontFamilies = null; defaultWhitespace = null; maxRegions = -1; maxLines = -1; maxLinesPerRegion = -1; maxChars = -1; maxCharsPerRegion = -1; maxCharsPerLine = -1; } private void resetGlobalState(boolean restart) { } @Override public String getName() { return NAME; } @Override public Collection<OptionSpecification> getLongOptionSpecs() { return longOptions.values(); } @Override public int parseLongOption(List<String> args, int index) { Reporter reporter = context.getReporter(); String arg = args.get(index); int numArgs = args.size(); String option = arg; assert option.length() > 2; option = option.substring(2); if (option.equals("default-background-color")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); defaultBackgroundColor = args.get(++index); } else if (option.equals("default-color")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); defaultColor = args.get(++index); } else if (option.equals("default-font-families")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); defaultFontFamilies = args.get(++index); } else if (option.equals("default-whitespace")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); defaultWhitespace = args.get(++index); } else if (option.equals("font")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); if (fontSpecificationFileNames == null) fontSpecificationFileNames = new java.util.ArrayList<String>(); fontSpecificationFileNames.add(args.get(++index)); } else if (option.equals("font-directory")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); fontSpecificationDirectoryPath = args.get(++index); } else if (option.equals("line-breaker")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); lineBreakerName = args.get(++index); } else if (option.equals("max-regions")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); String count = args.get(++index); try { maxRegions = Integer.parseInt(count); } catch (NumberFormatException e) { throw new InvalidOptionUsageException(option, reporter.message("*KEY*", "bad count syntax: {0}", count)); } } else if (option.equals("max-lines")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); String count = args.get(++index); try { maxLines = Integer.parseInt(count); } catch (NumberFormatException e) { throw new InvalidOptionUsageException(option, reporter.message("*KEY*", "bad count syntax: {0}", count)); } } else if (option.equals("max-lines-per-region")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); String count = args.get(++index); try { maxLinesPerRegion = Integer.parseInt(count); } catch (NumberFormatException e) { throw new InvalidOptionUsageException(option, reporter.message("*KEY*", "bad count syntax: {0}", count)); } } else if (option.equals("max-chars")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); String count = args.get(++index); try { maxChars = Integer.parseInt(count); } catch (NumberFormatException e) { throw new InvalidOptionUsageException(option, reporter.message("*KEY*", "bad count syntax: {0}", count)); } } else if (option.equals("max-chars-per-region")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); String count = args.get(++index); try { maxCharsPerRegion = Integer.parseInt(count); } catch (NumberFormatException e) { throw new InvalidOptionUsageException(option, reporter.message("*KEY*", "bad count syntax: {0}", count)); } } else if (option.equals("max-chars-per-line")) { if (index + 1 > numArgs) throw new MissingOptionArgumentException("--" + option); String count = args.get(++index); try { maxCharsPerLine = Integer.parseInt(count); } catch (NumberFormatException e) { throw new InvalidOptionUsageException(option, reporter.message("*KEY*", "bad count syntax: {0}", count)); } } else index = index - 1; return index + 1; } @Override public void processDerivedOptions() { // font specification directory File fontSpecificationDirectory = null; if (fontSpecificationDirectoryPath != null) { fontSpecificationDirectory = new File(fontSpecificationDirectoryPath); if (!fontSpecificationDirectory.exists()) throw new InvalidOptionUsageException("font-directory", "directory does not exist: " + fontSpecificationDirectoryPath); else if (!fontSpecificationDirectory.isDirectory()) throw new InvalidOptionUsageException("font-directory", "not a directory: " + fontSpecificationDirectoryPath); } // font specification files List<File> fontSpecificationFiles = null; if ((fontSpecificationFileNames != null) && !fontSpecificationFileNames.isEmpty()) { for (String name : fontSpecificationFileNames) { File fontSpecificationFile = new File(name); if (!fontSpecificationFile.exists()) throw new InvalidOptionUsageException("font", "file does not exist: " + name); else if (!fontSpecificationFile.isFile()) throw new InvalidOptionUsageException("font", "not a file: " + name); else { if (fontSpecificationFiles == null) fontSpecificationFiles = new java.util.ArrayList<File>(); fontSpecificationFiles.add(fontSpecificationFile); } } } // line and character breakers Reporter reporter = context.getReporter(); this.fontCache = new FontCache(fontSpecificationDirectory, fontSpecificationFiles, reporter); if (lineBreakerName == null) lineBreakerName = defaultLineBreakerName; LineBreaker lb = LineBreaker.getInstance(lineBreakerName); this.lineBreaker = lb; if (charBreakerName == null) charBreakerName = defaultCharacterBreakerName; LineBreaker cb = LineBreaker.getInstance(charBreakerName); this.charBreaker = cb; // defaults this.defaults = new Defaults(); // default background color if (defaultBackgroundColor != null) { com.skynav.ttv.model.value.Color[] retColor = new com.skynav.ttv.model.value.Color[1]; if (com.skynav.ttv.verifier.util.Colors.isColor(defaultBackgroundColor, new Location(), context, retColor)) defaults.setBackgroundColor(new Color(retColor[0].getRed(), retColor[0].getGreen(), retColor[0].getBlue(), retColor[0].getAlpha())); else throw new InvalidOptionUsageException("default-background-color", "invalid color syntax: " + defaultBackgroundColor); } // default color if (defaultColor != null) { com.skynav.ttv.model.value.Color[] retColor = new com.skynav.ttv.model.value.Color[1]; if (com.skynav.ttv.verifier.util.Colors.isColor(defaultColor, new Location(), context, retColor)) defaults.setColor(new Color(retColor[0].getRed(), retColor[0].getGreen(), retColor[0].getBlue(), retColor[0].getAlpha())); else throw new InvalidOptionUsageException("default-color", "invalid color syntax: " + defaultColor); } // default font families if (defaultFontFamilies != null) { List<com.skynav.ttv.model.value.FontFamily> families = new java.util.ArrayList<com.skynav.ttv.model.value.FontFamily>(); Object[] treatments = new Object[] { com.skynav.ttv.verifier.util.QuotedGenericFontFamilyTreatment.Allow }; if (com.skynav.ttv.verifier.util.Fonts.isFontFamilies(defaultFontFamilies, null, context, treatments, families)) { List<String> fontFamilies = new java.util.ArrayList<String>(families.size()); for (com.skynav.ttv.model.value.FontFamily f : families) fontFamilies.add(f.toString()); defaults.setFontFamilies(fontFamilies); } else throw new InvalidOptionUsageException("default-font-families", "invalid font families syntax: " + defaultFontFamilies); } // default visibility if ((Boolean) context.getExternalParameters().getParameter("forcedDisplay")) defaults.setVisibility(Visibility.HIDDEN); // default whitespace if (defaultWhitespace != null) { try { defaults.setWhitespace(Whitespace.valueOf(defaultWhitespace.toUpperCase())); } catch (IllegalArgumentException e) { throw new InvalidOptionUsageException("default-whitespace", "invalid whitespace syntax: " + defaultWhitespace); } } } @Override public List<Area> layout(Document d) { List<Area> areas = null; if (d != null) { Element root = d.getDocumentElement(); if (root != null) { LayoutState ls = makeLayoutState(); if (isElement(root, isdSequenceElementName)) areas = layoutISDSequence(root, ls); else if (isElement(root, isdInstanceElementName)) areas = layoutISDInstance(root, ls); warnOnCounterViolations(ls); } } return (areas != null) ? areas : new java.util.ArrayList<Area>(); } @Override public void clear(boolean all) { if (all) { if (fontCache != null) fontCache.clear(); if (lineBreaker != null) lineBreaker.clear(); if (charBreaker != null) charBreaker.clear(); } } protected LayoutState makeLayoutState() { return initializeLayoutState(createLayoutState()); } protected LayoutState createLayoutState() { return new BasicLayoutState(context); } protected LayoutState initializeLayoutState(LayoutState ls) { return ls.initialize(fontCache, getLineBreakIterator(), getCharacterBreakIterator(), getDefaults()); } protected LineBreakIterator getLineBreakIterator() { LineBreaker lb = lineBreaker; return (lb != null) ? lb.getIterator(context.getReporter()) : null; } protected LineBreakIterator getCharacterBreakIterator() { LineBreaker cb = charBreaker; return (cb != null) ? cb.getIterator(context.getReporter()) : null; } protected Defaults getDefaults() { return defaults; } protected List<Area> layoutISDSequence(Element e, LayoutState ls) { List<Area> areas = new java.util.ArrayList<Area>(); for (Element c : getChildElements(e)) { if (isElement(c, isdInstanceElementName)) areas.addAll(layoutISDInstance(c, ls)); } return areas; } protected List<Area> layoutISDInstance(Element e, LayoutState ls) { List<Area> areas = new java.util.ArrayList<Area>(); try { double begin = Double.parseDouble(e.getAttribute("begin")); double end = Double.parseDouble(e.getAttribute("end")); Extent cellResolution = parseCellResolution(Documents.getAttribute(e, ttpCellResolutionAttrName, null)); if (cellResolution == null) cellResolution = defaults.getCellResolution(); ls.pushCanvas(e, begin, end, cellResolution); Extent extent = ls.getExternalExtent(); double w = extent.getWidth(); double h = extent.getHeight(); boolean clip = ls.getExternalOverflow().clips(); ls.pushViewport(e, w, h, clip); WritingMode wm = ls.getExternalWritingMode(); TransformMatrix ctm = ls.getExternalTransform(); Visibility visibility = ls.getVisibility(e); ls.pushReference(e, 0, 0, w, h, wm, ctm, visibility); for (Element c : getChildElements(e)) { if (isElement(c, isdRegionElementName)) layoutRegion(c, ls); else if (isElement(c, isdComputedStyleSetElementName)) ls.saveStyles(c); } ls.pop(); ls.pop(); areas.add(ls.pop()); } catch (NumberFormatException x) { } return areas; } private Extent parseCellResolution(String value) { if (value != null) { Integer[] minMax = new Integer[] { 2, 2 }; Object[] treatments = new Object[] { NegativeTreatment.Error, ZeroTreatment.Error }; List<Integer> integers = new java.util.ArrayList<Integer>(); if (Integers.isIntegers(value, new Location(), context, minMax, treatments, integers)) return new Extent(integers.get(0), integers.get(1)); } return null; } protected void layoutRegion(Element e, LayoutState ls) { Display display = ls.getDisplay(e); if (display == Display.NONE) return; Extent extent = ls.getExtent(e); double w = extent.getWidth(); double h = extent.getHeight(); Point origin = ls.getPosition(e, extent); double x = origin.getX(); double y = origin.getY(); boolean clip = ls.getOverflow(e).clips(); ls.pushViewport(e, w, h, clip); WritingMode wm = ls.getWritingMode(e); TransformMatrix ctm = ls.getTransform(e); Visibility visibility = ls.getVisibility(e); ls.pushReference(e, x, y, w, h, wm, ctm, visibility); for (Element c : getChildElements(e)) { if (isElement(c, ttBodyElementName)) layoutBody(c, ls); } AreaNode r = ls.peek(); if (r instanceof ReferenceArea) alignBlockAreas((ReferenceArea) r, ls.getReferenceAlignment()); ls.pop(); ls.pop(); } protected void layoutBody(Element e, LayoutState ls) { Display display = ls.getDisplay(e); if (display == Display.NONE) return; Visibility visibility = ls.getVisibility(e); ls.pushBlock(e, visibility); for (Element c : getChildElements(e)) { if (isElement(c, ttDivisionElementName)) layoutDivision(c, ls); } ls.pop(); } protected void layoutDivision(Element e, LayoutState ls) { Display display = ls.getDisplay(e); if (display == Display.NONE) return; Visibility visibility = ls.getVisibility(e); ls.pushBlock(e, visibility); for (Element c : getChildElements(e)) { if (isElement(c, ttDivisionElementName)) { layoutDivision(c, ls); } else if (isElement(c, ttParagraphElementName)) { layoutParagraph(c, ls); } else if (isElement(c, ttImageElementName)) { layoutImage(c, ls); } } ls.pop(); } protected void layoutParagraph(Element e, LayoutState ls) { Display display = ls.getDisplay(e); if (display == Display.NONE) return; layoutParagraphs(e, new ParagraphCollector(newStyleCollector(ls)).collect(e), ls); } private StyleCollector newStyleCollector(LayoutState ls) { return new StyleCollector(null, context, ls.getFontCache(), defaults, ls.getExternalExtent(), ls.getReferenceExtent(), ls.getCellResolution(), ls.getWritingMode(), ls.getLanguage(), ls.getFont(), ls.getStyles()); } protected void layoutParagraphs(Element e, List<Paragraph> paragraphs, LayoutState ls) { for (Paragraph p : paragraphs) { layoutParagraph(p, ls); } } protected void layoutParagraph(Paragraph p, LayoutState ls) { Element e = p.getElement(); Visibility visibility = ls.getVisibility(e); ls.pushBlock(e, visibility); for (LineArea l : new ParagraphLayout(p, ls).layout()) { ls.addLine(l); } AreaNode b = ls.peek(); if (b instanceof BlockArea) alignLineAreas((BlockArea) b, ls); ls.pop(); } protected void layoutImage(Element e, LayoutState ls) { Display display = ls.getDisplay(e); if (display == Display.NONE) return; AreaNode a = ls.peek(); if ((a != null) && (a instanceof BlockArea)) { BlockArea b = (BlockArea) a; Visibility visibility = ls.getVisibility(e); Image image = ls.getForegroundImage(e); double w = image.getWidth(); double h = image.getHeight(); double ipd, bpd; if (ls.getWritingMode().isVertical()) { ipd = h; bpd = w; } else { ipd = w; bpd = h; } b.addChild(new BlockImageArea(e, ipd, bpd, visibility, image)); } } protected static List<Element> getChildElements(Element e) { return Documents.getChildElements(e); } protected static boolean isElement(Element e, QName qn) { return Documents.isElement(e, qn); } private void alignBlockAreas(ReferenceArea r, BlockAlignment alignment) { double measure = r.isVertical() ? r.getWidth() : r.getHeight(); double consumed = 0; for (AreaNode c : r.getChildren()) { consumed += c.getBPD(); } double available = measure - consumed; if (available > 0) { if (alignment == BlockAlignment.BEFORE) { AreaNode a = new BlockFillerArea(r.getElement(), 0, available); r.addChild(a, null); } else if (alignment == BlockAlignment.AFTER) { AreaNode a = new BlockFillerArea(r.getElement(), 0, available); r.insertChild(a, r.firstChild(), null); } else if (alignment == BlockAlignment.CENTER) { double half = available / 2; AreaNode a1 = new BlockFillerArea(r.getElement(), 0, half); AreaNode a2 = new BlockFillerArea(r.getElement(), 0, half); r.insertChild(a1, r.firstChild(), null); r.insertChild(a2, null, null); } else { // no-op } } else if (available < 0) { r.setOverflow(-available); } } private void alignLineAreas(BlockArea b, LayoutState ls) { BlockAlignment alignment = ls.getReferenceAlignment(); ReferenceArea r = ls.getReferenceArea(); double measure = r.isVertical() ? r.getWidth() : r.getHeight(); double consumed = 0; int numChildren = 0; for (AreaNode c : b.getChildren()) { consumed += c.getBPD(); ++numChildren; } double available = measure - consumed; if (available > 0) { if (alignment == BlockAlignment.BEFORE) { // no-op } else if (alignment == BlockAlignment.AFTER) { // no-op } else if (alignment == BlockAlignment.CENTER) { // no-op } else { justifyLineAreas(b, measure, consumed, numChildren, alignment); } } else if (available < 0) { b.setOverflow(-available); } } private void justifyLineAreas(BlockArea b, double measure, double consumed, int numChildren, BlockAlignment alignment) { double available = measure - consumed; if (alignment == BlockAlignment.JUSTIFY) alignment = BlockAlignment.SPACE_BETWEEN; int numFillers; if (alignment == BlockAlignment.SPACE_AROUND) { numFillers = numChildren + 1; } else if (alignment == BlockAlignment.SPACE_BETWEEN) { numFillers = numChildren - 1; } else numFillers = 0; double fill; if (numFillers > 0) fill = available / numFillers; else fill = 0; if (fill > 0) { List<AreaNode> children = new java.util.ArrayList<AreaNode>(b.getChildren()); for (AreaNode c : children) { AreaNode f = new BlockFillerArea(b.getElement(), 0, fill); if ((c == children.get(0)) && (alignment == BlockAlignment.SPACE_BETWEEN)) continue; else b.insertChild(f, c, null); } if (alignment == BlockAlignment.SPACE_AROUND) { AreaNode f = new BlockFillerArea(b.getElement(), 0, fill); b.insertChild(f, null, null); } } } private void warnOnCounterViolations(LayoutState ls) { Reporter reporter = context.getReporter(); ls.finalizeCounters(); int regions = ls.getCounter(LayoutState.Counter.REGIONS_IN_CANVAS); if ((maxRegions >= 0) && (regions > maxRegions)) reporter.logWarning(reporter.message("*KEY*", "Regions per canvas limit exceeded, {0} present, must not exceed {1}.", regions, maxRegions)); int lines = ls.getCounter(LayoutState.Counter.LINES_IN_CANVAS); if ((maxLines >= 0) && (lines > maxLines)) reporter.logWarning(reporter.message("*KEY*", "Lines per canvas limit exceeded, {0} present, must not exceed {1}.", lines, maxLines)); int linesPerRegion = ls.getCounter(LayoutState.Counter.MAX_LINES_IN_REGION); if ((maxLinesPerRegion >= 0) && (linesPerRegion > maxLinesPerRegion)) reporter.logWarning(reporter.message("*KEY*", "Lines per region limit exceeded, {0} present, must not exceed {1}.", linesPerRegion, maxLinesPerRegion)); int chars = ls.getCounter(LayoutState.Counter.CHARS_IN_CANVAS); if ((maxChars >= 0) && (chars > maxChars)) reporter.logWarning(reporter.message("*KEY*", "Characters per canvas limit exceeded, {0} present, must not exceed {1}.", chars, maxChars)); int charsPerRegion = ls.getCounter(LayoutState.Counter.MAX_CHARS_IN_REGION); if ((maxCharsPerRegion >= 0) && (charsPerRegion > maxCharsPerRegion)) reporter.logWarning(reporter.message("*KEY*", "Characters per region limit exceeded, {0} present, must not exceed {1}.", charsPerRegion, maxCharsPerRegion)); int charsPerLine = ls.getCounter(LayoutState.Counter.MAX_CHARS_IN_LINE); if ((maxCharsPerLine >= 0) && (charsPerLine > maxCharsPerLine)) reporter.logWarning(reporter.message("*KEY*", "Characters per line limit exceeded, {0} present, must not exceed {1}.", charsPerLine, maxCharsPerLine)); } }