Proper way to implement custom Css attribute with Itext and html2Pdf - itext

I'm using Itext 7 and their html2Pdf lib.
Is there a way to implement for example cmyk colors.
.wootWorkingCMYK-color{
color: cmyk( 1 , 0.69 , 0.08 , 0.54);
}
I know the itext core part pretty good, looking for away to use the html2Pdf side. I'm aware of the CssApplierFactory but this seems to be to far up the chain.

Well, of course there is a way of processing custom CSS properties like cmyk colors, but unfortunately the code would be quite bulky and you will need to write quite some code for different cases. I will show how to apply custom color for font, but e.g. for backgrounds, borders or other cases you will need to write separate code in a similar way. Reason behind it is that iText layout structure, although designed with HTML/CSS in mind, is not 100% similar and has some differences you have to code around.
Having that said, if you can fork, build and use your custom version from sources, this is the way I would advice to go. Although it has drawbacks like having to rebase to get updates, the solution would be simpler and more generic. To do that, search for usages of CssUtils.parseRgbaColor in pdfHTML module, and you will find that it is used in BackgroundApplierUtil, BorderStyleApplierUtil, FontStyleApplierUtil, OutlineApplierUtil. There you will find code like
if (!CssConstants.TRANSPARENT.equals(cssColorPropValue)) {
float[] rgbaColor = CssUtils.parseRgbaColor(cssColorPropValue);
Color color = new DeviceRgb(rgbaColor[0], rgbaColor[1], rgbaColor[2]);
float opacity = rgbaColor[3];
transparentColor = new TransparentColor(color, opacity);
} else {
transparentColor = new TransparentColor(ColorConstants.BLACK, 0f);
}
Which I belive you can tweak to process cmyk as well, knowing that you know core part pretty well.
Now, the solution without custom pdfHTML version is to indeed start with implementing ICssApplierFactory, or subclassing default implementation DefaultCssApplierFactory. We are mostly interested in customizing implementation of SpanTagCssApplier and BlockCssApplier, but you can consult with DefaultTagCssApplierMapping to get the full list of appliers and cases they are used in, so that you can decide which of them you want to process in your code.
I will show you how to add support for custom color space for font color in the two main applier classes I mentioned and you can work from there.
private static class CustomCssApplierFactory implements ICssApplierFactory {
private static final ICssApplierFactory DEFAULT_FACTORY = new DefaultCssApplierFactory();
#Override
public ICssApplier getCssApplier(IElementNode tag) {
ICssApplier defaultApplier = DEFAULT_FACTORY.getCssApplier(tag);
if (defaultApplier instanceof SpanTagCssApplier) {
return new CustomSpanTagCssApplier();
} else if (defaultApplier instanceof BlockCssApplier) {
return new CustomBlockCssApplier();
} else {
return defaultApplier;
}
}
}
private static class CustomSpanTagCssApplier extends SpanTagCssApplier {
#Override
protected void applyChildElementStyles(IPropertyContainer element, Map<String, String> css, ProcessorContext context, IStylesContainer stylesContainer) {
super.applyChildElementStyles(element, css, context, stylesContainer);
String color = css.get("color2");
if (color != null) {
color = color.trim();
if (color.startsWith("cmyk")) {
element.setProperty(Property.FONT_COLOR, new TransparentColor(parseCmykColor(color)));
}
}
}
}
private static class CustomBlockCssApplier extends BlockCssApplier {
#Override
public void apply(ProcessorContext context, IStylesContainer stylesContainer, ITagWorker tagWorker) {
super.apply(context, stylesContainer, tagWorker);
IPropertyContainer container = tagWorker.getElementResult();
if (container != null) {
String color = stylesContainer.getStyles().get("color2");
if (color != null) {
color = color.trim();
if (color.startsWith("cmyk")) {
container.setProperty(Property.FONT_COLOR, new TransparentColor(parseCmykColor(color)));
}
}
}
}
}
// You might want a safer implementation with better handling of corner cases
private static DeviceCmyk parseCmykColor(String color) {
final String delim = "cmyk(), \t\r\n\f";
StringTokenizer tok = new StringTokenizer(color, delim);
float[] res = new float[]{0, 0, 0, 0};
for (int k = 0; k < 3; ++k) {
if (tok.hasMoreTokens()) {
res[k] = Float.parseFloat(tok.nextToken());
}
}
return new DeviceCmyk(res[0], res[1], res[2], res[3]);
}
Having that custom code, you should configure the ConverterProperties accordingly and pass it to HtmlConverter:
ConverterProperties properties = new ConverterProperties();
properties.setCssApplierFactory(new CustomCssApplierFactory());
HtmlConverter.convertToPdf(..., properties);
You might have noticed that I used color2 instead of color, and this is for a reason. pdfHTML has a mechanism of CSS property validation (as browsers do as well), to discard invalid CSS properties when calculating effective properties for an element. Unfortunately, there is no mechanism of customizing this validation logic currently and of course it treats cmyk colors as invalid declarations at the moment. Thus, if you really want to have custom color property, you will have to preprocess your HTML and replace declarations like color: cmyk... to color2: cmyk.. or whatever the property name you might want to use.
As I mentioned at the start of the answer, my recommendation is to build your own custom version :)

Related

Syntax Coloring for a custom list of words in Eclipse editor

We are using a custom editor for a specific kind of files. Now the problem here is, i am new to this eclipse related stuff. I have to highlight list of words with a specific colour. I checked online found some rules but i dont have basic idea how to use them. It would be great if someone help me how to add a specific colour for given list of words in custom editor.
Thanks in advance.
You can use this code put it in your reconciler class : `
private final TextAttribute wordAttribute = new TextAttribute(new Color(Display.getCurrent(), new RGB(0, 0, 255));
public static final String[] keywords = { "define your keywords here" };
....................................................................
RuleBasedScanner scanner = new RuleBasedScanner();
IRule[] rules = new IRule[5]; //set as many rules as you want just change the number in the bracket
....................................................................
WordRule rule = new WordRule(new IWordDetector() {//the IWordDetector class is an anonymous class so you'll have to define and override this two functions below as you want
#Override
public boolean isWordStart(char c) {
// TODO Auto-generated method stub
return Character.isJavaIdentifierStart(c);
}
#Override
public boolean isWordPart(char c) {
// TODO Auto-generated method stub
return Character.isJavaIdentifierStart(c);
}
});
for (int i = 0; i < keywords.length; i++) {
rule.addWord(keywords[i], new Token(wordAttribute));
}
rules[0] = rule;
scanner.setRules(rules);
}`
I think it should color your words as it does to me.

Eclipse RCP ListProvider tweaking

I have problem with changing this image list provider in to thumbnail provider. In case of need I will post View for it too.
public Object[] getElements(Object inputElement) {
if (iDirname == null)
return null;
File dir = new File(iDirname);
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File directory, String filename) {
if (filename.endsWith("jpg") || (filename.endsWith("bmp")) || (filename.endsWith("png") || (filename.endsWith("JPG") || (filename.endsWith("BMP")) || (filename.endsWith("PNG")))))
return true;
else
return false;
}
};
String[] dirList = null;
if (dir.isDirectory()) {
dirList = dir.list(filter);
for (int i=0; i<dirList.length;++i){
//dirList2[i] = new Image(device, dirList2[i]); added this to try passing array of Images - failed.
dirList[i] = iDirname + File.separatorChar + dirList[i];
}
}
return dirList;
}
And the view
public void createPartControl(Composite parent) {
iViewer = new ListViewer(parent);
iViewer.setContentProvider(new DirListProvider());
getSite().setSelectionProvider(iViewer);
makeActions();
hookContextMenu();
contributeToActionBars();
}
I don't know how to change provided path lists to the thumbnail displaying. Should I get the provided content in to Array and iterate through it creating Images? If so how?
Thanks in advance for your help.
EDIT:
I added
ImageDescriptor[] dirList = null;
if (dir.isDirectory()) {
String[] dirList2 = dir.list(filter);
for (int i=0; i<dirList2.length;++i){
dirList[i] = ImageDescriptor.createFromImageData(new ImageData(iDirname + File.separatorChar + dirList2[i]));
//dirList[i] = iDirname + File.separatorChar + dirList[i];
}
}
return dirList;
but this is not showing anything at all.
When you are telling me to use Composite, is it my parent variable? I still don't know how to display the images from paths passed by ListProvider. I am really green in this :/
What you are missing here is a LabelProvider. You can use a LabelProvider to provide an image for each element in your viewer's input.
However, Francis Upton is right, I don't think ListViewer will really suit your needs as you will end up with a single column of images. Although you won't be able to add the images directly to your Composite, you will need to set them as the background image of a label.
There are a couple of other things to consider:
You need to dispose() of your Images once you're done with them as they use up System handles. Therefore you need to keep track of the Images you create in your getElements(Object) method.
If the directories you are reading the images from do not already contain thumbnails, you will need to scale the images before presenting them on your UI.
Remember, the array type you return from your ContentProvider's getElements(Object) method defines the type that will get passed into your LabelProvider's methods. So you started off returning an array of strings representing paths to the images. Your LabelProvider would need to load these into images to be returned from the provider's getImage method - but bear in mind what I said about disposing of these images! Then you switched to returning an Array of image descriptors, in this case you would need to cast your incoming Object to an ImageDescriptor and use that to create the Image in the getImage method. Maybe once you have this working you can think about whether this meets your needs, and then possibly look at doing a different implementation, such as the composite/gridlayout/label approach.
I would not use a ListViewer for this. I would just create a Composite and then using GridLayout set up the number of columns you want and margins and so forth, and then just add the images directly to the composite. As far as I know you cannot put arbitrary things like imagines in an SWT List, so the ListViewer is not going to help you. You can do all of this in the createPartControl method.

Overlay images in gxt Tree Panel

I'm developing a gxt application that provides a view for svn-like versioning: A TreePanel that displays a file system structure with files and folders having different states (new, modified, deleted, etc.).
I want to display a small overlay icon on each of the items to reflect its state. What I could do, is to create an icon for each state, but I don't want to do that, because I would end up in creating a large bundle of redundant images.
What would be the best way to implement overlay icon capabilities in trees or grids?
After checking the dom of the tree structure, I figured out that a tree icon is displayed using the background-url css attribute. To ensure the image is being displayed with correct size, the src attribute of the element is filled with a placeholder image url.
The trick is, to replace the placeholder image with the overlay icon, since it is displayed above the actual image.
To accomplish this, I extended ClippedImagePrototype to inject the url of the overlay image:
public class OverlayImagePrototype extends ClippedImagePrototype {
protected String overlayImageUrl = null;
public static AbstractImagePrototype create(ImageResource resource, String overlayUrl) {
return new OverlayImagePrototype(resource.getSafeUri(),
resource.getLeft(), resource.getTop(), resource.getWidth(),
resource.getHeight(), overlayUrl);
}
private OverlayImagePrototype(SafeUri url, int left, int top, int width,
int height, String overlayUrl) {
super(url, left, top, width, height);
this.overlayImageUrl = overlayUrl;
}
public ImagePrototypeElement createElement() {
ImagePrototypeElement imgEl = super.createElement();
if (overlayImageUrl != null) {
imgEl.setAttribute("src", overlayImageUrl);
}
return imgEl;
}
}
This is how I use OverlayImagePrototype in the IconProvider implementation:
tree.setIconProvider(new ModelIconProvider<ModelData>() {
public AbstractImagePrototype getIcon(ModelData model) {
return OverlayImagePrototype.create(model.get("icon"), model.get("overlayIconUrl"));
}
});

Does JasperReports support alternating gutter margins yet?

Many people who generate PDFs need to bind them. A good binding requires that every other page support an alternate margin size on its left and right sides. I know JasperReports did not support this in its 3.x series. Is this supported in the 4.x series?
You can accomplish marginMirroring as mentioned by Dave, by subclassing JRPdfExporter, overriding the method, exportReportToStream. Unfortunately, you will need to copy the source for this method into your override. In your override, you will modify the page loop, like so:
for(int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++)
{
int margin = marginLeft;
if (pageIndex % 2 == 1) margin = marginRight;
parameters.put(JRExporterParameter.OFFSET_X, margin);
setOffset();
...
The constructor for my subclass takes in the margins:
public MirroringJRPdfExporter(int left, int right, int top, int bottom) {
this.marginLeft = left;
this.marginRight = right;
this.marginTop = top;
this.marginBottom = bottom;
}
I took in top and bottom too, just in case I needed to mirror that for page flipping.
Another unfortunate note, exportReportToStream uses a helper, JRPdfExporterTagHelper, and calls 2 methods, init and setPdfWriter, which are protected, so your subclass will not compile unless you subclass the helper too and expose those methods to your subclass. I did this:
public class JRPdfExporterTagHelper extends
net.sf.jasperreports.engine.export.JRPdfExporterTagHelper {
protected JRPdfExporterTagHelper(JRPdfExporter exporter) {
super(exporter);
}
public void setPdfWriter2(PdfWriter pdfWriter) {
setPdfWriter(pdfWriter);
}
public void init2(PdfContentByte pdfContentByte) {
init(pdfContentByte);
}
}
Then, I call it like this:
MirroringJRPdfExporter exporter = new MirroringJRPdfExporter(72, 36, 44, 31);
exporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, output);
exporter.exportReport();
In JasperReports 6.x you can specify margins for even and odd pages separately in the report template (jrxml) by setting
<property name="net.sf.jasperreports.export.pdf.odd.page.offset.x" value="10"/>
<property name="net.sf.jasperreports.export.pdf.even.page.offset.x" value="-10"/>
An example can be found from the JasperReports sample file demo/samples/query/reports/QueryReport.jrxml. I found this solution in an issue.
The same can be set using the JRPdfExporter class when exporting the report to pdf in Java:
JRPdfExporter exporter = new JRPdfExporter();
SimplePdfReportConfiguration configuration = new SimplePdfReportConfiguration();
configuration.setOddPageOffsetX(10);
configuration.setEvenPageOffsetX(-10);
exporter.setConfiguration(configuration);
To work with jasper 5.6 besides the answer to #bigspotteddog I did:
#Override
protected PdfReportConfiguration getCurrentItemConfiguration() {
SimplePdfReportConfiguration config = new SimplePdfReportConfiguration();
PdfReportConfiguration currentItemConfiguration = super.getCurrentItemConfiguration();
config.setCollapseMissingBookmarkLevels(currentItemConfiguration.isCollapseMissingBookmarkLevels());
config.setForceLineBreakPolicy(currentItemConfiguration.isForceLineBreakPolicy());
config.setForceSvgShapes(currentItemConfiguration.isForceSvgShapes());
config.setIgnoreHyperlink(currentItemConfiguration.isIgnoreHyperlink());
config.setOverrideHints(currentItemConfiguration.isOverrideHints());
config.setSizePageToContent(currentItemConfiguration.isSizePageToContent());
config.setEndPageIndex(currentItemConfiguration.getEndPageIndex());
config.setExporterFilter(currentItemConfiguration.getExporterFilter());
config.setHyperlinkProducerFactory(currentItemConfiguration.getHyperlinkProducerFactory());
config.setPageIndex(currentItemConfiguration.getPageIndex());
config.setProgressMonitor(currentItemConfiguration.getProgressMonitor());
config.setStartPageIndex(currentItemConfiguration.getStartPageIndex());
config.setOffsetX(margin);
return config;
}
and :
margin = marginLeft;
if (pageIndex % 2 == 1) margin = marginRight;
in the loop.

Using the Selection service on something that is *not* a JFace view

I am building an image Editor as an Eclipse plugin.
I would like to use the Properties view to view & edit properties of the model underneath the image. Accordingly I am calling ..
getSite().setSelectionProvider( this );
.. within createPartControl, and implementing the ISelectionProvider interface in my EditorPart implementation, so that the model is returned as the selection (which must therefore implement the ISelection interface).
The next step is for the Editor to implement IAdaptable to supply an adapter for the selected object.
My problem however is that getAdapter is never called with IPropertySource.class, and therefore the Properties View never gets what it needs to make sense of the image model.
Your help is much appreciated.
M.
The answer in the end broke down into a few pieces ...
1.) When your selection does change (if a user has zoomed into the image, for example) be sure to tell Eclipse this. It won't happen otherwise.
2.) When sending your SelectionChangedEvent, wrap up your IAdaptable in a StructuredSelection object - otherwise the Properties view will ignore it.
This boiled down to the following method
public void fireSelectionChanged()
{
final SelectionChangedEvent event = new SelectionChangedEvent( this, new StructuredSelection( this ) );
Object[] listeners = selectionChangedListeners.getListeners();
for (int i = 0; i < listeners.length; ++i)
{
final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
SafeRunnable.run(new SafeRunnable() {
public void run() {
l.selectionChanged( event );
}
});
}
}
... on an class that implemented ISelectionProvider & IAdaptable.
M.