Illustrator Javascript - Render Swatch Legend - LAB Colour Values Incorrect

Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Hi All!

So, a question regarding the failry well known fab Script written by John Wundes:

renderSwatchLegend.jsx 

I need to create a chart that specifies just the LAB values underneith the swatches that the script creates, but curiously, the LAB values that the script produces are always wrong - they're always out by a digit and I don't know why. 

 

If I double click the swatch in the swatch palette, the LAB value is correct/as it should be, it is just the text that the script produces and lays onto the artboard that is incorrect. Please see following image. Any ideas why this glitch is happening?

 

This glitch happens consistently on every single import - the lab value that the script generates onto the artboard is never the correct value. Help! 

 

Screenshot 2020-09-16 151903.jpg

 

 

 

 

 

 

 

TOPICS
Bug , Draw and design , Scripting , Third party plugins

Views

1.2K

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

ALSO - I forgot to add, does anyone know what the G number is (below the LAB values?)

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Most likely Gray scale on a 0-10 range.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Thanks larry! 

 

Do you know if it is necessary to display this value along with LAB? (If I have to show LAB that is)

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

I don't think the script is correct in showing the Lab values.

It looks like it is showing the Lab values of the color when it is converted to CMYK.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Argh! IF so, that is super frustrating. LAB values are the most accurate print values there are, so it's crucial they match. 

 

What is puzzling to me though, is that the script does any conversions at all - WHY???????

It should just be extracting the actual data from the swatch, so why is it changing numerical values at all? 

 

The actual swatch values are exacting, certainly from Pantones. So why does the script then start to ínterpret' them? Makes no sense at all. 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

we don't have access to LAB values in the scripting API, that's the reason conversion was needed

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

An Book Color, Carlos, is that available in scripting?

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

The Scripting API recognizes Pantone Colors as SpotLAB, but when we dig down to get the actual color values we get either CMYK or RGB according to your Document Color Mode.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

It comes down to rounding, decimals get lost from Pantone to CMYK (or RGB) then to LAB.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

I’m not sure that is correct. Rounding would make sense if there were half values etc but this isn’t the case.

On every single imported swatch - I.e on every swatch the script runs through and presents on the art board, the values are always one digit more out, without fail. The L value often remains the same, but the A B Values are always off by one digit. This isn’t rounding, there’s something going wrong.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

It is not rounding.

A Pantone color cannot be reproduced in CMYK, that's why it is a special mixed ink.

But Lab can describe any color we can see.

When you convert from Lab to CMYK you will lose color information

When you convert it back to Lab, you will never get the original values back.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Thanks for the input, but I'm getting completely lost and baffled here. 

why is the script doing any conversions of anything? I'm not sure if I have explained the problem correctly here. 

I'll try br clearer. 

when the script runs, it is placing the swatch on the art board - this is absolutely fine, I can select that placed square of colour that the script creates and then open up its swatch values by double clicking the actual swatch in the swatches palette - the values are all correct, LAB values also. 

The problem is nothing to do with the swatch or the colour itself. 

the problem, is the text that the script is placing onto the swatch. The LAB value in the text in creates, does not match up with the lab value of the actual swatch. 

Something funky is happening with the script that is extrapolating the LAB value from the swatches, it is deleting digits and then creating the overlay text. There is nothing wrong with the color/swatch itself, just the overlaid text info. 

is that clearer? 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

it's been clear since the very start Nathan, what I'm trying to tell you is that the LAB values you see in the UI are not accessible to scripting. The Scripting API provides a conversion method, we get the values in CMYK or RGB then we convert them to LAB. That's where the rounding comes from.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Hi Carlos, yes sorry - all is clear now, I understand what is happening. 

I've never understood why Illustrator doesn't display LAB natively, but this is obviously why the script is needing to take the cmyk/RGB data and then produce the Lab value (That is slightly different). 

Makes sense to me now, thank you for explaining and repeating this to me. 

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Knowing what we know now, the script will only give correct values for CMYK swatches in a CMYK document an RGB swatches in an RGB document. All other values are calculated using the current color settings.

swatches script.png

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

The plot thickens!

 

My problem is that I download my pantones from Pantone Colour Manager as 'LAB' swatches - I can only download as either LAB or sRGB and this is for textile printing. 

My file is set up using Adobe RGB space as I'm digitally printing fabric. 

Long story short, the LAB value that the script creates and writes out is never going to be precise, so in my user case, I'll just have to use the RGB values that the script writes out - if I'm thinking this through correctly, those RGB values the script writes out should be correct? 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Yes as far as I can conclude, the RGB values that the script gives you are the same values you would get when you open the Pantone swatch and manually switch to RGB. 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

hmmm. there might be hope, let me make some testing...The plot thickens!

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Yes, the plot is getting very thick!

Let me add to my latest reply: the RGB values that the script gives you are the same values you would get when you open the Pantone swatch and manually switch to RGB: In an RGB document.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

try this updated version

 

/////////////////////////////////////////////////////////////////
// Render Swatch Legend v1.3 -- CC
//>=--------------------------------------
//
//  This script will generate a legend of rectangles for every swatch in the main swatches palette.
//  You can configure spacing and value display by configuring the variables at the top
//  of the script.
//   update: v1.1 now tests color brightness and renders a white label if the color is dark.
//   update: v1.2 uses adobe colour converter, rather than rgb colour conversion for a closer match.
//   update: v1.3 adds multiple colour space values based on array printColors.

// LAB values by Carlos Canto - 09/16/2020
// reference: https://community.adobe.com/t5/illustrator/illustrator-javascript-render-swatch-legend-lab-colour-values-incorrect/m-p/11438710?page=2#M244722
//>=--------------------------------------
// JS code (c) copyright: John Wundes ( john@wundes.com ) www.wundes.com
// copyright full text here:  http://www.wundes.com/js4ai/copyright.txt
//
// Edits by Adam Green (@wrokred) www.wrokred.com

//////////////////////////////////////////////////////////////////
doc = activeDocument,
    swatches = doc.swatches,
    cols = 4, // number of columns in group
    displayAs = "RGBColor", //or "CMYKColor"
    printColors = ["RGB", "CMYK", "LAB", "GrayScale"], // RGB, CMYK, LAB and/or GrayScale
    colorSeparator = " ", // Character used to separate the colours eg "|" output = R: XXX|G: XXX|B: XXX
    textSize = 10, // output text size value in points
    rectRef = null,
    textRectRef = null,
    textRef = null,
    swatchColor = null,
    w = 150;
h = 120,
    h_pad = 10,
    v_pad = 10,
    t_h_pad = 10,
    t_v_pad = 10,
    x = null,
    y = null,
    black = new GrayColor(),
    white = new GrayColor();
black.gray = 100;
white.gray = 0;
activeDocument.layers[0].locked = false;
var newGroup = doc.groupItems.add();
newGroup.name = "NewGroup";
newGroup.move(doc, ElementPlacement.PLACEATBEGINNING);
for (var c = 2, len = swatches.length; c < len; c++) {
    var swatchGroup = doc.groupItems.add();
    swatchGroup.name = swatches[c].name;
    x = (w + h_pad) * ((c - 2) % cols);
    y = (h + v_pad) * (Math.round(((c) + .03) / cols)) * -1;
    rectRef = doc.pathItems.rectangle(y, x, w, h);
    swatchColor = swatches[c].color;
    rectRef.fillColor = swatchColor;
    textRectRef = doc.pathItems.rectangle(y - t_v_pad, x + t_h_pad, w - (2 * t_h_pad), h - (2 * t_v_pad));
    textRef = doc.textFrames.areaText(textRectRef);
    textRef.contents = swatches[c].name + "\r" + getColorValues(swatchColor);
    textRef.textRange.fillColor = is_dark(swatchColor) ? white : black;
    textRef.textRange.size = textSize;
    rectRef.move(swatchGroup, ElementPlacement.PLACEATBEGINNING);
    textRef.move(swatchGroup, ElementPlacement.PLACEATBEGINNING);
    swatchGroup.move(newGroup, ElementPlacement.PLACEATEND);
}

function getColorValues(c, spot) {
    if (c.typename) {
        if (c.typename == "SpotColor") {
            return getColorValues(c.spot.color, c.spot);
        };
        switch (c.typename) {
            case "RGBColor":
                sourceSpace = ImageColorSpace.RGB;
                colorComponents = [c.red, c.green, c.blue];
                break;
            case "CMYKColor":
                sourceSpace = ImageColorSpace.CMYK;
                colorComponents = [c.cyan, c.magenta, c.yellow, c.black];
                break;
            case "LabColor":
                sourceSpace = ImageColorSpace.LAB;
                colorComponents = [c.l, c.a, c.b];
                break;
            case "GrayColor":
                sourceSpace = ImageColorSpace.GrayScale;
                colorComponents = [c.gray];
                break;
        }
        var outputColors = new Array();
        for (var i = printColors.length - 1; i >= 0; i--) {
            targetSpace = ImageColorSpace[printColors[i]];
            
            if (printColors[i] == 'LAB' && spot && spot.spotKind == 'SpotColorKind.SPOTLAB') {
                outputColors[i] = spot.getInternalColor();
            }
            else {
                outputColors[i] = app.convertSampleColor(sourceSpace, colorComponents, targetSpace, ColorConvertPurpose.previewpurpose);
            }
            for (var j = outputColors[i].length - 1; j >= 0; j--) {
                outputColors[i][j] = printColors[i].charAt(j) + ": " + Math.round(outputColors[i][j]);
                if (j == outputColors[i].length - 1) {
                    outputColors[i][j] += "\r";
                };
            };
            outputColors[i] = outputColors[i].join(colorSeparator);
        };
        return outputColors.join("");
    }
    return "Non Standard Color Type";
}

function is_dark(c) {
    if (c.typename) {
        switch (c.typename) {
            case "CMYKColor":
                return (c.black > 50 || (c.cyan > 50 && c.magenta > 50)) ? true : false;
            case "RGBColor":
                return (c.red < 100 && c.green < 100) ? true : false;
            case "GrayColor":
                return c.gray > 50 ? true : false;
            case "SpotColor":
                return is_dark(c.spot.color);
                return false;
        }
    }
}

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

You did it! 

The Lab values are exactly the same as the ones from the Pantone swatch.

And in an RGB document the RGB colors are the same as the conversion you see when you select RGB in the Color panel or in the Swatch options.

Same for the CMYK conversion in a CMYK document.

But CMYK or Gray numbers are totally different from the conversion you would expect when you selected them in an RGB document and RGB and Gray are different in a CMYK document. I wonder where that comes from.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

Hi Ton thanks so much for your input and testing, it's so helpful.

 

what do you mean exactly in your last sentence there? Could you possibly do a screen shot to explain? 

thanks in advance 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Sep 16, 2020 Sep 16, 2020

Copy link to clipboard

Copied

I cannot WAIT to try this in the morning! 
You absolute star Carlos, thanks a million if this works - which judging by Ton's input below, it does! 
You have both been incredibly helpful thank you thank you 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 26, 2022 Jul 26, 2022

Copy link to clipboard

Copied

@CarlosCanto 

 

A small edit i would make is adjust #23

 

swatches = doc.swatches

 

 

 to get only selected 

 

swatches = doc.swatches.getSelected()

 

 

Now it will only return the once you select. It needs some minor work, now the columns get a bit weird. Seems he skips the first 2, those arent needed. But he uses the 2 in other places as well.
 
EDIT  - Fixed column issue
Got it working now correctly, for those interested. See below

 

EDIT 2 - Added HEX code

I made another edit, it now also outputs HEX code. This one was a bit tricky. code is not as clean. But im no pro at this

 

EDIT 3 - Split color componenten yes/no

Okay last feature, added an option to split by color component or show color component as name then all the values. If you adjust the bool splitColorComponents. I did add 2 function from a different script. This was needed todo the convert from CMYtoRGB. I think i can get rif od it, part of that looks the same function already in this script. It now also works when you have a CMYK document, it still returns the HEX value

renderSwatchLegend_v144.png

 

 

 

/////////////////////////////////////////////////////////////////
// Render Swatch Legend v1.4.4 -- CC
//>=--------------------------------------
//
//  This script will generate a legend of rectangles for every swatch in the main swatches palette.
//  You can configure spacing and value display by configuring the variables at the top
//  of the script.
//   update: v1.1 now tests color brightness and renders a white label if the color is dark.
//   update: v1.2 uses adobe colour converter, rather than rgb colour conversion for a closer match.
//   update: v1.3 adds multiple colour space values based on array printColors.
//   update: v1.4.1 Updated by CarlCanto > https://community.adobe.com/t5/illustrator-discussions/illustrator-javascript-render-swatch-legend-lab-colour-values-incorrect/m-p/11437592
//   update: v1.4.2 Only on selected Rombout Versluijs
//   update: v1.4.3 Added HEX colors Rombout Versluijs
//   update: v1.4.4 Added Split by Color Component Rombout Versluijs

// LAB values by Carlos Canto - 09/16/2020
// reference: https://community.adobe.com/t5/illustrator/illustrator-javascript-render-swatch-legend-lab-colour-values-incorrect/m-p/11438710?page=2#M244722
//>=--------------------------------------
// JS code (c) copyright: John Wundes ( john@wundes.com ) www.wundes.com
// copyright full text here:  http://www.wundes.com/js4ai/copyright.txt
//
// Edits by Adam Green (@wrokred) www.wrokred.com

//////////////////////////////////////////////////////////////////
doc = activeDocument,
swatches = doc.swatches.getSelected(),
cols = 4, // number of columns in group
displayAs = "RGBColor", //or "CMYKColor"
printColors = ["HEX", "RGB", "CMYK", "LAB", "GrayScale"], // RGB, CMYK, LAB and/or GrayScale
colorSeparator = " ", // Character used to separate the colours eg "|" output = R: XXX|G: XXX|B: XXX
splitColorComponents = false;
textSize = 10, // output text size value in points
rectRef = null,
textRectRef = null,
textRef = null,
swatchColor = null,
w = 150;
h = 120,
h_pad = 10,
v_pad = 10,
t_h_pad = 10,
t_v_pad = 10,
x = null,
y = null,
black = new GrayColor(),
white = new GrayColor();
black.gray = 100;
white.gray = 0;
activeDocument.layers[0].locked = false;
var newGroup = doc.groupItems.add();
newGroup.name = "NewGroup";
newGroup.move(doc, ElementPlacement.PLACEATBEGINNING);
for (var c = 0, len = swatches.length; c < len; c++) {
    var swatchGroup = doc.groupItems.add();
    swatchGroup.name = swatches[c].name;
    x = (w + h_pad) * ((c) % cols);
    y = (h + v_pad) * (Math.round(((c+2) + .03) / cols)) * -1;
    rectRef = doc.pathItems.rectangle(y, x, w, h);
    swatchColor = swatches[c].color;
    rectRef.fillColor = swatchColor;
    textRectRef = doc.pathItems.rectangle(y - t_v_pad, x + t_h_pad, w - (2 * t_h_pad), h - (2 * t_v_pad));
    textRef = doc.textFrames.areaText(textRectRef);
    textRef.contents = swatches[c].name + "\r" + getColorValues(swatchColor);
    textRef.textRange.fillColor = is_dark(swatchColor) ? white : black;
    textRef.textRange.size = textSize;
    rectRef.move(swatchGroup, ElementPlacement.PLACEATBEGINNING);
    textRef.move(swatchGroup, ElementPlacement.PLACEATBEGINNING);
    swatchGroup.move(newGroup, ElementPlacement.PLACEATEND);
}

function getColorValues(c, spot) {
    if (c.typename) {
        if (c.typename == "SpotColor") {
            return getColorValues(c.spot.color, c.spot);
        };
        switch (c.typename) {
            case "RGBColor":
                sourceSpace = ImageColorSpace.RGB;
                colorComponents = [c.red, c.green, c.blue];
                break;
            case "CMYKColor":
                sourceSpace = ImageColorSpace.CMYK;
                colorComponents = [c.cyan, c.magenta, c.yellow, c.black];
                break;
            case "LabColor":
                sourceSpace = ImageColorSpace.LAB;
                colorComponents = [c.l, c.a, c.b];
                break;
            case "GrayColor":
                sourceSpace = ImageColorSpace.GrayScale;
                colorComponents = [c.gray];
                break;
        }
        var outputColors = new Array();
        for (var i = printColors.length - 1; i >= 0; i--) {
            colorType = printColors[i] == "HEX" ? "Indexed": printColors[i];
            targetSpace = ImageColorSpace[colorType] ;
            
            if (printColors[i] == 'LAB' && spot && spot.spotKind == 'SpotColorKind.SPOTLAB') {
                outputColors[i] = spot.getInternalColor();
            } else if(printColors[i] == 'HEX') {
                if (app.activeDocument.documentColorSpace == DocumentColorSpace.CMYK) {
                    colorArray = [c.cyan, c.magenta, c.yellow, c.black];
                    // [Math.round(c), Math.round(m), Math.round(y), Math.round(k)]
                    rgbConv = app.convertSampleColor(ImageColorSpace["CMYK"], colorArray, ImageColorSpace["RGB"], ColorConvertPurpose.defaultpurpose);          
                    outputColors[i] = [rgbConv[0].toString(16), rgbConv[1].toString(16), rgbConv[2].toString(16)];
                } else{
                    outputColors[i] = [c.red.toString(16), c.green.toString(16), c.blue.toString(16)];

                }
            }
            else {
                outputColors[i] = app.convertSampleColor(sourceSpace, colorComponents, targetSpace, ColorConvertPurpose.previewpurpose);
            }
            for (var j = outputColors[i].length - 1; j >= 0; j--) {
                colorComp = splitColorComponents == true ? printColors[i].charAt(j) + ": " : "";
                if(isNaN(outputColors[i][j])){
                    outputColors[i][j] = colorComp + outputColors[i][j];
                } else {
                    outputColors[i][j] = colorComp + Math.round(outputColors[i][j]);
                }
                if (j == outputColors[i].length - 1) {
                    outputColors[i][j] += "\r";
                };
            };
            outputColors[i] = outputColors[i].join(colorSeparator);
            if(!splitColorComponents) outputColors[i] = printColors[i]+" "+outputColors[i]
        };
        return outputColors.join("");
    }
    return "Non Standard Color Type";
}

function is_dark(c) {
    if (c.typename) {
        switch (c.typename) {
            case "CMYKColor":
                return (c.black > 50 || (c.cyan > 50 && c.magenta > 50)) ? true : false;
            case "RGBColor":
                return (c.red < 100 && c.green < 100) ? true : false;
            case "GrayColor":
                return c.gray > 50 ? true : false;
            case "SpotColor":
                return is_dark(c.spot.color);
                return false;
        }
    }
}

 

 

 

 

 

 

 

 

 

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines