Code Monkey home page Code Monkey logo

psd.js's Introduction

PSD.js

Build Status Help Contribute to Open Source

A general purpose PSD parser written in Coffeescript. Based off of PSD.rb. It allows you to work with a Photoshop document in a manageable tree structure and find out important data such as:

  • Document structure
  • Document size
  • Layer/folder size + positioning
  • Layer/folder names
  • Layer/folder visibility and opacity
  • Font data (via psd-enginedata)
    • Text area contents
    • Font names, sizes, and colors
  • Color mode and bit-depth
  • Vector mask data
  • Flattened image data
  • Layer comps

Runs in both NodeJS and the browser (using browserify). There are still some pieces missing that are present in PSD.rb, such as layer comp filtering, a built-in renderer, and many layer info blocks. The eventual goal is full feature parity with PSD.rb.

Installation

PSD.js has no native dependencies. Simply add psd to your package.json or run npm install psd.

Documentation

Note: work in progress

Annotated source code documentation is available here. PROTIP: if you're wondering how to access various metadata from a layer, you'll want to see this file.

Usage

PSD.js works almost exactly the same in the browser and NodeJS.

NodeJS Example

var PSD = require('psd');
var psd = PSD.fromFile("path/to/file.psd");
psd.parse();

console.log(psd.tree().export());
console.log(psd.tree().childrenAtPath('A/B/C')[0].export());

// You can also use promises syntax for opening and parsing
PSD.open("path/to/file.psd").then(function (psd) {
  return psd.image.saveAsPng('./output.png');
}).then(function () {
  console.log("Finished!");
});

Browser Example

var PSD = require('psd');

// Load from URL
PSD.fromURL("/path/to/file.psd").then(function(psd) {
  document.getElementById('ImageContainer').appendChild(psd.image.toPng());
});

// Load from event, e.g. drag & drop
function onDrop(evt) {
  PSD.fromEvent(evt).then(function (psd) {
    console.log(psd.tree().export());
  }); 
}

Traversing the Document

To access the document as a tree structure, use psd.tree() to get the root node. From there, work with the tree using any of these methods:

  • root(): get the root node from anywhere in the tree
  • isRoot(): is this the root node?
  • children(): get all immediate children of the node
  • hasChildren(): does this node have any children?
  • childless(): opposite of hasChildren()
  • ancestors(): get all ancestors in the path of this node (excluding the root)
  • siblings(): get all sibling tree nodes including the current one (e.g. all layers in a folder)
  • nextSibling(): gets the sibling immediately following the current node
  • prevSibling(): gets the sibling immediately before the current node
  • hasSiblings(): does this node have any siblings?
  • onlyChild(): opposite of hasSiblings()
  • descendants(): get all descendant nodes not including the current one
  • subtree(): same as descendants but starts with the current node
  • depth(): calculate the depth of the current node (root node is 0)
  • path(): gets the path to the current node

If you know the path to a group or layer within the tree, you can search by that path. Note that this always returns an Array because layer/group names do not have to be unique. The search is always scoped to the descendants of the current node, as well.

psd.tree().childrenAtPath('Version A/Matte');
psd.tree().childrenAtPath(['Version A', 'Matte']);

Accessing Layer Data

To get data such as the name or dimensions of a layer:

node = psd.tree().descendants()[0];
node.get('name');
node.get('width');

PSD files also store various pieces of information in "layer info" blocks. See this file for all of the possible layer info blocks that PSD.js parses (in LAYER_INFO). Which blocks a layer has varies from layer-to-layer, but to access them you can do:

node = psd.tree().descendants()[0]
node.get('typeTool').export()
node.get('vectorMask').export()

Exporting Data

When working with the tree structure, you can recursively export any node to an object. This does not dump everything, but it does include the most commonly accessed information.

console.log(psd.tree().export());

Which produces something like:

{ children: 
   [ { type: 'group',
       visible: false,
       opacity: 1,
       blendingMode: 'normal',
       name: 'Version D',
       left: 0,
       right: 900,
       top: 0,
       bottom: 600,
       height: 600,
       width: 900,
       children: 
        [ { type: 'layer',
            visible: true,
            opacity: 1,
            blendingMode: 'normal',
            name: 'Make a change and save.',
            left: 275,
            right: 636,
            top: 435,
            bottom: 466,
            height: 31,
            width: 361,
            mask: {},
            text: 
             { value: 'Make a change and save.',
               font: 
                { name: 'HelveticaNeue-Light',
                  sizes: [ 33 ],
                  colors: [ [ 85, 96, 110, 255 ] ],
                  alignment: [ 'center' ] },
               left: 0,
               top: 0,
               right: 0,
               bottom: 0,
               transform: { xx: 1, xy: 0, yx: 0, yy: 1, tx: 456, ty: 459 } },
            image: {} } ] } ],
    document: 
       { width: 900,
         height: 600,
         resources: 
          { layerComps: 
             [ { id: 692243163, name: 'Version A', capturedInfo: 1 },
               { id: 725235304, name: 'Version B', capturedInfo: 1 },
               { id: 730932877, name: 'Version C', capturedInfo: 1 } ],
            guides: [],
            slices: [] } } }

You can also export the PSD to a flattened image. Please note that, at this time, not all image modes + depths are supported.

png = psd.image.toPng(); // get PNG object
psd.image.saveAsPng('path/to/output.png').then(function () {
  console.log('Exported!');
});

This uses the full rasterized preview provided by Photoshop. If the file was not saved with Compatibility Mode enabled, this will return an empty image.

psd.js's People

Contributors

meltingice avatar mgenglder avatar fabulousduck avatar iamgqb avatar glinkis avatar sngjuk avatar samccone avatar 0x-leen avatar carlosruizascacibar avatar basicallydan avatar loginovilya avatar arcanis avatar linkesch avatar schneems avatar

Stargazers

Chen,Yongze avatar Hengyu You avatar  avatar Công Long avatar Yeji Kim avatar Lyon avatar 文峰 avatar  avatar GreenYoshi (Wang Han) avatar jiangtao avatar 东方飞鱼 avatar Jiawei Wang avatar  avatar Greg Zhang avatar Franck Chan avatar  avatar Coro avatar  avatar Afe Notes avatar 秦少卫   avatar Luciano Meniconi avatar 小小子 avatar smallverse avatar  avatar Houtaroy avatar betgar avatar HJ Wang avatar Ody avatar WhitrayHB avatar mooremok avatar 朱仙变 avatar iRyan avatar Daniel Boroujerdi avatar fefeding avatar  avatar jarzzzi avatar 悦悦 avatar  avatar 灰熊 avatar  avatar naya avatar Jean-Philippe Deblonde avatar  avatar buhui_wang avatar Frastio Agustian avatar Jack avatar yovi avatar  avatar  avatar 史蒂芬孙 avatar Zickie Loox avatar  avatar  avatar 樊小书生 avatar  avatar  avatar nansen zhu avatar Utku avatar Gabriel Pozzi avatar Doctor Lee avatar code_for_the_poem avatar qk avatar  avatar min avatar ChenSida avatar Hacker&胖大星 avatar  avatar  avatar Maikah Jusu avatar Shanta Conine avatar chunhei2008 avatar  avatar Ling Zhan avatar LeeSeongJinCa avatar Meeken avatar  avatar Stanislav Butsenko avatar Jieting avatar Ouvill avatar Sutu avatar 夜 avatar xzhluo avatar  avatar Jason avatar Kevin James O'Dea avatar 美兰十三 avatar QQM avatar  avatar Leo avatar  avatar Brian Hull avatar  avatar  avatar hero1990 avatar 達也 avatar Zheng Gao avatar Thinking80s avatar  avatar  avatar  avatar

Watchers

Eugene Liang avatar  avatar evandrix avatar Kelly Nichols avatar Christian Hochfilzer avatar Yushiro avatar Valentin Dubois avatar 情封 avatar Jasmine Hegman avatar  avatar James Cloos avatar Ben Haim avatar 平江 avatar Javin avatar Michael Anthony avatar Kevin avatar MD5500 avatar  avatar Emmanuel Ulloa Arguello avatar Olexander avatar 周遥 avatar 孙万鑫 avatar Mark Du avatar gloriagao avatar Agua Lei avatar Guillermo Pajares avatar trumpli avatar Philipp Blum avatar Artem Churyukin avatar Chuck Lu avatar  avatar  avatar 延杰.郑 avatar Noemí Sánchez del Río avatar  avatar  avatar bourbon avatar  avatar Rahul Y Gupta avatar Choas01 avatar Albert Tony avatar ryan lin avatar oO avatar hoxiudimdim avatar thelastrider avatar Amir B. avatar zhouhongwei avatar 746964750 avatar Denis Yevtushenko avatar  avatar Hoàng Anh Dũng avatar wenhsing avatar joliejuke avatar Edam avatar  avatar  avatar  avatar  avatar  avatar Tiana Dreessen avatar

psd.js's Issues

Segmentation Fault on layer.export()

I'm working my way through a PSD exporting each layer and on a specific PSD the 41st item of 125 throws a "Segmentation fault" when I call export() on it. I have no further information provided to me beyond that.

How to include psd.js inside a requirejs application

How can I include/use this require.js module, PSD.js, inside my require.js application?

If I use the script like this example below, it works as it should:

<script type="text/javascript" src="assets/scripts/vendor/psd.min.js"></script>
<script type="text/javascript">
    (function () {
        var PSD = require('psd');

        document.getElementById('dropzone').addEventListener('dragover', onDragOver, true);
        document.getElementById('dropzone').addEventListener('drop', onDrop, true);

        function onDragOver(e) {
            e.stopPropagation();
            e.preventDefault();
            e.dataTransfer.dropEffect = 'copy';
        }

        function onDrop(e) {
            e.stopPropagation();
            e.preventDefault();

            PSD.fromEvent(e).then(function (psd) {
                var data = JSON.stringify(psd.tree().export(), undefined, 2);
                document.getElementById('data').innerHTML = data;
                document.getElementById('image').appendChild(psd.image.toPng());
            });
        }
    }());
</script>

But if I try to include it by doing the recommanded requirejs way it doesn't;

requirejs.config({
    paths: {
        'psd': 'vendor/psd.min'
        }
});

// Load our app module 'main.js' and pass it to our definition function
requirejs(['app'], function (App) {
    // The "app" dependency is passed in as "App"
    App.initialize();
});

define('modules/customwidgets.add_widget', [ 'psd'], function () {
    var Widget = function () {
    };
    Widget.prototype = {
        config: {},
        init: function (htmlElement) {
            "use strict";

            // This module
            var me = this;

           console.log('It loads!');

           var PSD = require('psd');

            document.getElementById('dropzone').addEventListener('dragover', onDragOver, true);
            document.getElementById('dropzone').addEventListener('drop', onDrop, true);

            function onDragOver(e) {
                e.stopPropagation();
                e.preventDefault();
                e.dataTransfer.dropEffect = 'copy';
            }

            function onDrop(e) {
                e.stopPropagation();
                e.preventDefault();

                PSD.fromEvent(e).then(function (psd) {
                    var data = JSON.stringify(psd.tree().export(), undefined, 2);
                    document.getElementById('data').innerHTML = data;
                    document.getElementById('image').appendChild(psd.image.toPng());
                });
            }
        }
    };
    return Widget;
});

Support for linked smart objects

Is it possible we'll see support for linked smart objects in near future? Maybe it's already there and I'm just not doing something right...

I am thinking of having several PSD templates where I have one or more linked smart objects with transformations, distortions, layers, etc. Then in a web service users can upload their own images that would be swapped with the linked smart objects. This would make it really easy to render complex and custom images. You could literally just copy the wanted images into a tmp folder (that originally contained linked smart objects) and render the custom image. It would be immensely helpful as transformations, overlays etc is much easier to just do in Photoshop than imagemagick or similar.

Simple example could be a 3D rendered living room where users could upload their own pictures which would be added to the picture frames in the render.

save as png with layer effects

I would like to use saveAsPng or toPng in a way that maintains their final appearance, currently when exporting specific layer as png the effects are not preserved.
How can it be done?
thanks.

ask about getting pixel data from mask

I want to get pixel data from mask of layer. I've searched and worked on this issue for weeks but come no result. someone please help me to solve this problem? please please! thank you so much

Wrong Sizes of layer

Hi, first of all thanks for made this great tool :)

I recently find out that the sizes and coords of the layers are wrong. The main problem are the coords (left, top, bottom, right) because you get the width and height with that info. In some layers you will have 3px wrong in others 4px and others 1px or no error. Is a kind of weird.

Anyone has a solution for it? I tested with a lot of files, and different versions of photoshop and the issue still happen.

An example: a layer that in photoshop has top: 130, left:50, bottom: 220, right 110 , this plugin return this info when is parsing : top: 128, left: 48, bottom: 221, right: 112.

The main problem is not all the layers has the same difference, so you can't made a function or simply subtract the appropriate number.

Thanks for the help!

Webpack support

Here are the changes I had to make in order to build via webpack

marvelapp@3f7f27b
marvelapp@b6a183e

I didn't send a pull request as I didn't think you'd want them in the current format (they're more proof of concept than anything else), also I didn't add a command in Cakefile.

You can clone that repo, run npm i -g webpack, npm i, then just simply webpack to build.

font shadow missing in exported png file

I have a psd file, with one font layer. When exporting png file, the font shadow is missing. Here's my code:
PSD = require('psd');

PSD.open('psd/1.psd').then(function(psd) {
psd.tree().descendants().forEach(function(node) {
if(node.isGroup()) return true;
node.saveAsPng("./output1/" + node.name + ".png").catch(function(err) {
console.log(err.stack);
});
});
}).then(function() {
console.log('finished');
});

Help me please

Export all layers into separate png

I get JSON doc that contains information about the parsed PSD, but is there any way I can get image library with images of every layers in the psd files ?

Remove cakefile and move build steps into package.json

Having a cakefile means that a developer needs coffeescript installed globally* to build this tool.

I propose to make this repo more portable by instead of using the task system within the cakefile to rather put the tasks as npm run-script tasks. Thus making the only dependency for building the tool node and npm.

* or they could use the installed bin version via ./node_modules/.bin/coffee

LICENSE?

Seems I can't for the life of me find a license this code is under, only historical posts of being taken over by large companies and so on.

Can this be clarified in the readme, or the convention, a LICENSE file?

Png-Dom-Object in NodeJS Mode

I would like to create a png preview of an psd in an Electron App.

But with toPng() I get this Object which isn't mountable to the DOM instead of a Browser Image Object :
screen shot 2016-05-10 at 16 36 32

If I unterstand it right, psd.js runs in NodeJS mode because Electron has NodeJS build in the Browser/Renderer too. Which isn't the normal use case of node.
I tried to load psd.js with requirejs, but that didn't change anything.

var requirejs = require('requirejs');
var PSD = requirejs('psd');

PSD.fromURL("/path/to/file.psd").then(function(psd) { 
  document.getElementById('ImageContainer').appendChild(psd.image.toPng());
});

The Usage - "NodeJS Example" works fine, the "Browser Example" don't

Can I load psd.js in Browser mode or get the Png Image Object somehow?

Thanks a lot!

how to get the data of a single layer?

I parse a psd file and print out the information of a single layer like this:

console.log(psd.tree().descendants()[2]);

When I find there is a data property in the result.

{ layer:
    { file:
        { data: <Buffer 38 42 50 53 00 01 00 00 00 00 00 00 00 04 00 00 05 36 00 00 02 ee 00 08 00 03 00 00 00 00 00 00 75 30 38 42 49 4d 04 04 00 00 00 00 00 2f 1c 01 5a 00 ... >,
        pos: 838570 },
    header:
    { file: [Object],
        sig: '8BPS',
        version: 1,
        channels: 4,
        height: 1334,
        rows: 1334,
        width: 750,
        cols: 750,
        depth: 8,
        mode: 3 },
...

But when I invoke fs.writeFileSync to save the data of the layer, it saves the image of the psd.

Finally I find the data of every descendant is the same as the data of the whole psd

I also find that some other properties in descendant object such as header.file.data mask.file.data blendMode.file.data image.file.data are the same as layer.file.data.

So how to get the data of a single layer ?

How to distinguish layers with masks and shape layers?

When exporting a layer as PNG file, the result differs depending on the layer type. So I want to tell whether a layer is

  • a shape layer / a fill layer with vector mask applied
  • a layer with vector mask applied
  • a layer with normal layer mask applied

I created a test file as shown below.
PSD layer structure

Now I go ahead and export each layer as a PNG file, I got the result like this.
Export each PSD layer as a PNG file

The two shape layers, or fill layers with vector masks, appears to be the same as they are in Photoshop when exported as PNG files. However, when a normal pixel layer has vector mask or layer mask applied, the exported PNG file only shows the layer content, regardless of the masks.

If I call the following method on each of these layers: shape_gradient, shape_solid_color, and vector_mask

layer.get('vectorMask').paths

The results are similar. I can't tell shape layers (or fill layers with vector masks) from normal pixel layers with vector masks.

And I don't know how to check if a layer has a normal layer mask applied. Looks like the layer_mask.coffee is dealing with a concept different from Photoshop's layer mask.

Any ideas how to distinguish these layers?

some files in layer_info doesn't work

Hi Ryan LeFevre , I have a problem when use psd,js.
I create a psd file using PhotoShop CS6, and parse it in Node.js using psd.js. But I find the value of locked field always false even if some layers were locked.And I find the loaded value is false.
I never learned Coffee, so I don't know what the lazy_execute.coffee did.In my eyes, the method parse of the locked.coffee file seems never to run.
The above is my problem. My English is poor,and hope I express the problem clearly.

TypeError on saving layer as PNG

I'm getting this error intermittently, when calling node.layer.image.saveAsPng(imagePath):

TypeError: pxData.copy is not a function at [object Object].Filter._filterNone (/var/task/node_modules/psd/node_modules/pngjs/lib/filter.js:213:16) at [object Object].Filter.filter (/var/task/node_modules/psd/node_modules/pngjs/lib/filter.js:196:27) at [object Object].Packer.pack (/var/task/node_modules/psd/node_modules/pngjs/lib/packer.js:54:23) at [object Object].<anonymous> (/var/task/node_modules/psd/node_modules/pngjs/lib/png.js:68:22) at nextTickCallbackWith0Args (node.js:415:9) at process._tickDomainCallback (node.js:385:13)

I've logged node.layer.image to the console, and it has always looked reasonable, any suggestions on how to fix, or debug this would be greatly appreciated.

saveAsPng not working?

trying to use saveAsPng in a grunt task but its not generating any errors, and not creating any output PNG either, any idea what im doing wrong?

var PSD = require('psd');

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    'convertPSD': {

        target: { prop1:1 },

        options: {
          defProperty: "defValue",
          dFiles: { 
            cwd: './',   
            src: ['*.psd'], 
            dest: './'  

          }
        }
    }

  });



  grunt.registerTask('default', 'convertPSD');

  grunt.registerMultiTask('convertPSD', 'Convert PSDs', function() {

    var curTask = this,
        opts = curTask.options();

    if (!curTask.files.length && 'dFiles' in opts) {
      var df = opts.dFiles;

      curTask.files = grunt.file.expandMapping(df.src, df.dest , df);

    }

this.files.forEach(function(file){

      console.log(file.src[0])

      PSD.open(file.src[0]).then(function (psd) {
        return psd.image.saveAsPng("./TST.png");
      }).then(function () {
        console.log("converted"+file.src[0]+" to "+file.src[0]+".png");
      });
})


});


};

Save layer with offsets

There are top and left variables in the layer that aren't used (Ie the image is shoved to the upper left). Is there a way to respect the layer properties on the server side?

Preview PSD in Browser

Hi,

I am trying to view the file example.psd in chrome, but without success.
Please tell me what I'm doing wrong.
Thank you.

This is my code:

<!DOCTYPE html>
<html>
<head>
  <script type="text/javascript" src="../../dist/psd.min.js"></script>
</head>

<body>
  <div id="image"></div>

  <script type="text/javascript">
  (function () {
    var PSD = require('psd');

    PSD.fromURL("../images/example.psd").then(function(psd) {
      document.getElementById('image').appendChild(psd.image.toPng());
    });
  }());
  </script>
</body>
</html>

where is psd.js now?

seems noaheverett deleted it? =(
was not a good decision to move the ownership

error parsing PSD file

I am trying to parse a PSD file and getting the following error
/Users/james/gameframe/portal-simple/node_modules/psd/lib/psd/nodes/root.coffee:27
layer.right = psd.header.width;
^
TypeError: Cannot read property 'width' of null
at Function.module.exports.Root.layerForPsd (/Users/james/gameframe/portal-simple/node_modules/psd/lib/psd/nodes/root.coffee:13:29)
at new Root (/Users/james/gameframe/portal-simple/node_modules/psd/lib/psd/nodes/root.coffee:20:16)
at PSD.module.exports.PSD.tree (/Users/james/gameframe/portal-simple/node_modules/psd/lib/psd.coffee:63:16)
at /Users/james/gameframe/portal-simple/psd.js:24:28
at Object.oncomplete (fs.js:107:15)

The file seems valid and I can open it up in Photoshop. Any help would be greatly appreciated. Thanks in advance.

How to get data from layer

I 'm using "obj1.layer.mask" but it false some cases

Mask {
file:
File {
data: <Buffer 38 42 50 53 00 01 00 00 00 00 00 00 00 03 00 00 19 2a 00 00 05 ef 00 08 00 03 00 00 00 00 00 5b 5f 52 38 42 49 4d 04 04 00 00 00 00 00 97 1c 01 5a 00 ... >,
pos: 100752008 },
top: -1584,
right: 1519,
bottom: 4858,
left: 0,
size: 20,
width: 1519,
height: 6442,
relative: false,
disabled: false,
invert: false,
defaultColor: 255,
flags: 0 }

case 1

Not parsing font size properly

Greetings, I'm testing with a basic PSD file and I have 3 layers with text. All different sizes. And it's reporting two with size 5.58981 when the font size should be 16.8pt and another layer is getting parsed as 13 when it should be 65pt. Also, will the font weight be added soon?

how to save layer with mask data to png

please tell me the way to extract layer with mask data to png

psd.tree().descendants().reverse().forEach(function (layer) {
    if (layer.name == 'Layer 113 copy 6') {
        layer.layer.image.saveAsPng("D:/wsite/abc.png").catch(function (err) {
            console.log(err.stack);
        });
    return;
    }
});

psd.js return
abc

paint.net return
test

image offset missing from exported png

image x,y offset is missing from exported png files.

This information is useful for reassembly, layering, placement in qml or similar ...

node examples/node/export_layers.js
$ identify output/Logo_Glyph.png
output/Logo_Glyph.png PNG 142x179 142x179+0+0 8-bit sRGB 3.27KB 0.000u 0:00.000

Note the geometry is 142x179+0+0.
Should be (see below): 142x179+379+211

$ identify -format '%[label] %[scene] %[width]x%[height]%[X]%[Y]\n' examples/images/example.psd
..
Logo_Glyph 2 142x179+379+211
..
$ convert examples/images/example.psd[2] Logo_Glyph.png
$ identify Logo_Glyph.png
Logo_Glyph.png PNG 142x179 142x179+379+211 8-bit sRGB 3.81KB 0.000u 0:00.000

Image Size?

I can't find any examples of how to access the PSD width and height and any other meta information available on the PSD as a whole.

gulp - Error: Parsing file psd.coffee: Line 1

events.js:85
throw er; // Unhandled 'error' event
^
Error: Parsing file /......../node_modules/psd/lib/psd.coffee: Line 1: Unexpected string
at Deps.parseDeps (/......../node_modules/gulp-browserify/node_modules/browserify/node_modules/module-deps/index.js:339:28)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.